using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Windows.Forms;
using EnvDTE;
using EnvDTE80;
using Microsoft.VisualStudio.CommandBars;

namespace Core
{
	public class Ide
	{
		private readonly DTE2 _dte;
		private readonly SolutionEvents _solutionEvents;
	    private readonly ProjectItemsEvents _projectItemsEvents;

		public Ide(DTE2 dte)
		{
			_dte = dte;
			_solutionEvents = ((Events2)_dte.Events).SolutionEvents;
		    _projectItemsEvents = ((Events2)_dte.Events).ProjectItemsEvents;
		}

		public DTE2 Dte
		{
			get { return _dte; }
		}

		public SolutionEvents SolutionEvents
		{
			get { return _solutionEvents; }
		}

        public ProjectItemsEvents ProjectItemsEvents
        {
            get { return _projectItemsEvents; }
        }

        public CodeDocument ActiveDocument
        {
            get { return _dte.ActiveDocument == null ? null : new CodeDocument(_dte.ActiveDocument); }
        }

        public NativeWindow MainWindow
		{
			get
			{
				var window = new NativeWindow();
				window.AssignHandle((IntPtr)_dte.MainWindow.HWnd);
				return window;
			}
		}

		public bool SolutionIsOpen
		{
			get { return _dte.Solution.IsOpen; }
		}

		public Commands2 Commands
		{
			get { return (Commands2) _dte.Commands; }
		}

		public string SelectedText
		{
			get
			{
				if (_dte.ActiveDocument == null)
					return "";

				TextSelection selection = (TextSelection)_dte.ActiveDocument.Selection;

				return selection.Text;
			}
		}

		public string WordUnderCarat
		{
			get
			{
				if (_dte.ActiveDocument == null)
					return "";

				TextSelection selection = (TextSelection)_dte.ActiveDocument.Selection;

				EditPoint beginPoint = selection.ActivePoint.CreateEditPoint();
				beginPoint.WordLeft(1);

				EditPoint endPoint = beginPoint.CreateEditPoint();
				endPoint.WordRight(1);

				return beginPoint.GetText(endPoint).Trim();
			}
		}

		public OutputWindowPane CreateOutputPane(string title)
		{
			return _dte.ToolWindows.OutputWindow.OutputWindowPanes.Add(title);
		}

		public List<string> FindFiles(string directory, List<string> extensions)
		{
			var matches = new List<string>();

			AddMatchingFilesToList(extensions, new DirectoryInfo(directory), matches);

			return matches;
		}

		private static void AddMatchingFilesToList(List<string> extensions, DirectoryInfo directoryInfo, List<string> matches)
		{
			try
			{
				if (extensions.Count == 0)
					matches.AddRange(directoryInfo.GetFiles().Select(fileInfo => fileInfo.FullName));
				else
				{
					foreach (var extension in extensions)
						matches.AddRange(directoryInfo.GetFiles(extension).Select(fileInfo => fileInfo.FullName));
				}
			}
			catch (Exception)
			{
			}

			foreach (DirectoryInfo subDirectoryInfo in directoryInfo.GetDirectories())
			{
				try
				{
					AddMatchingFilesToList(extensions, subDirectoryInfo, matches);
				}
				catch (Exception)
				{
				}
			}
		}

		public List<string> FindFilesInSolution(List<string> extensions = null)
		{
			var sourceFiles = new List<string>(1000);
			var trimmedExtensions = extensions == null ? null : extensions.Select(extension => extension.TrimStart(new[] {'*'})).ToList();

			foreach (Project project in _dte.Solution)
				AddSourceFiles(project.ProjectItems, sourceFiles, trimmedExtensions);

			return sourceFiles;
		}

		private static void AddSourceFiles(ProjectItems items, ICollection<string> sourceFiles, List<string> extensions)
		{
			if (null == items)
				return;

			foreach (ProjectItem item in items)
			{
				switch (item.Kind)
				{
					case Constants.vsProjectItemKindSolutionItems:
						if (item.SubProject != null)
							AddSourceFiles(item.SubProject.ProjectItems, sourceFiles, extensions);
						break;

					case Constants.vsProjectItemKindPhysicalFolder:
					case Constants.vsProjectItemKindVirtualFolder:
						AddSourceFiles(item.ProjectItems, sourceFiles, extensions);
						break;

					case Constants.vsProjectItemKindPhysicalFile:
						string path = item.FileNames[0];
						if (extensions == null || extensions.Count == 0 || extensions.Contains(Path.GetExtension(path)))
							sourceFiles.Add(path);
						break;
				}
			}
		}

		class ExtensionFrequency
		{
			public string Extension;
			public int Frequency;
		};

		public List<string> FindExtensionsInSolution()
		{
			var extensions = new List<ExtensionFrequency>();

			foreach (Project project in _dte.Solution)
				AddExtensions(project.ProjectItems, extensions);

			var sorted = from extension in extensions
						 orderby extension.Frequency descending
						 select extension.Extension;

			return new List<string>(sorted);
		}

		private static void AddExtensions(ProjectItems items, List<ExtensionFrequency> extensions)
		{
			if (null == items)
				return;

			foreach (ProjectItem item in items)
			{
				switch (item.Kind)
				{
					case Constants.vsProjectItemKindSolutionItems:
						if (item.SubProject != null)
							AddExtensions(item.SubProject.ProjectItems, extensions);
						break;

					case Constants.vsProjectItemKindPhysicalFolder:
					case Constants.vsProjectItemKindVirtualFolder:
						AddExtensions(item.ProjectItems, extensions);
						break;

					case Constants.vsProjectItemKindPhysicalFile:
						string extension = Path.GetExtension(item.get_FileNames(0));
						int index = extensions.FindIndex((a) => a.Extension == extension);
						if (index >= 0)
							extensions[index].Frequency++;
						else
							extensions.Add(new ExtensionFrequency { Extension = extension, Frequency = 1 });
						break;
				}
			}
		}

		public CommandBar FindCommandBar(string name)
		{
			CommandBar menuBar = ((CommandBars) _dte.CommandBars)["MenuBar"];

			try
			{
				CommandBarPopup popup = (CommandBarPopup) menuBar.Controls[name];
				return popup.CommandBar;
			}
			catch (ArgumentException)
			{
			}

			return (CommandBar) _dte.Commands.AddCommandBar(name, vsCommandBarType.vsCommandBarTypeMenu, menuBar, 4);
		}

        public bool OpenFile(string path)
        {
            return _dte.ItemOperations.OpenFile(path) != null;
        }

        public void SplitDocumentsVertically()
        {
            _dte.ExecuteCommand("Window.TileVertically", "");
        }

        public void CloseAllDocuments()
        {
            _dte.Documents.CloseAll();
        }

		public void ActivateOutputWindow()
		{
			_dte.ToolWindows.OutputWindow.Parent.Activate();
		}
	}
}
